import time

import pandas as pd
import ccxt
import json
import logging
import threading
import traceback
from datetime import datetime
from logging.handlers import SMTPHandler
from Function import *
from Config import *
from warnings import simplefilter

simplefilter(action='ignore', category=FutureWarning)
pd.set_option('display.max_rows', 1000)
pd.set_option('expand_frame_repr', False)  # 当列太多时不换行
pd.set_option('display.unicode.ambiguous_as_wide', True)  # 设置命令行输出时的列对齐功能
pd.set_option('display.unicode.east_asian_width', True)


def init_logger(cfg):
    logger = logging.getLogger('eth_boll')
    # logger.setLevel(logging.DEBUG)
    fm = logging.Formatter(cfg['format'])
    # fh = logging.FileHandler(cfg['file'])
    fh = logging.handlers.TimedRotatingFileHandler(cfg['file'], 'midnight', backupCount=0)

    fh.setFormatter(fm)
    fh.setLevel(cfg['file_level'])

    mail_handler = SMTPHandler(
        mailhost=(cfg['mail_host'], cfg['mail_host_port']),
        fromaddr=cfg['fromaddr'],
        toaddrs=cfg['toaddrs'],
        subject=cfg['subject'],
        credentials=(cfg['credential_name'],
                     cfg['credentials_key']))
    mail_handler.setLevel(cfg['mail_level'])
    mail_handler.setFormatter(fm)

    logger.addHandler(mail_handler)
    logger.setLevel(cfg['level'])
    logger.addHandler(fh)
    logger.propagate = False
    return logger


def main(exchange, cfg, symbol_config, logger):
    # =获取交易精度
    usdt_future_exchange_info(exchange, symbol_config)
    # =判断是否单向持仓，若不是程序退出
    _ = if_oneway_mode(exchange)
    if _:
        logger.info(_)
    pool = []  # 用来存放算法线程对象
    # ==========获取需要交易币种的历史数据==========
    # 获取数据
    max_candle_num = list(cfg['stg'].values())[0]['max_candle_num']  # TODO 每次获取的K线数量,暂时一个策略，直接从配置获取
    symbol_candle_data = get_binance_history_candle_data(exchange, symbol_config, time_interval, max_candle_num, logger)

    # =进入每次的循环
    while True:
        # ==========根据当前时间，获取策略下次执行时间，例如16:15。并sleep至该时间==========
        # ***** 需要放到，最前，行不行之前放在获取持仓之后，是因为他下市价单，所以十秒肯定成交了，但是我下算法单，
        # 所以需要在开始之前获取最新的持仓状态～ ***********
        run_time = sleep_until_run_time(time_interval, if_sleep=True)
        try:  # 先暂时这么实现，后面再实现连续ping交易所的功能。
            r = ping(exchange)
        except Exception as e:
            logger.warning('交易所无法ping通！')
        # ==========获取持仓数据==========
        # 初始化symbol_info，在每次循环开始时都初始化，防止上次循环的内容污染本次循环的内容。
        symbol_info_columns = ['账户权益', '分配比例', '分配资金', '持仓方向', '持仓量', '持仓收益', '持仓均价', '当前价格']
        symbol_info = pd.DataFrame(index=symbol_config.keys(), columns=symbol_info_columns)  # 转化为dataframe
        symbol_info['分配比例'] = pd.DataFrame(symbol_config).T['position']

        # 更新账户信息symbol_info
        symbol_info = binance_update_account(exchange, symbol_config, symbol_info)
        logger.info('持仓信息\n' + str(symbol_info))

        # 测试用代码
        # time.sleep(10)
        # run_time = datetime.now()

        # ==========获取最新的k线数据==========
        exchange.timeout = 1000  # 即将获取最新数据，临时将timeout设置为1s，加快获取数据速度
        # 获取数据
        recent_candle_num = 5
        recent_candle_data = single_threading_get_binance_candle_data(exchange, symbol_config, symbol_info,
                                                                      time_interval, run_time, recent_candle_num, logger)
        # 将最近的数据打印出
        for symbol in symbol_config.keys():
            logger.info(str(recent_candle_data[symbol].tail(min(2, recent_candle_num))))

        # 将symbol_candle_data和最新获取的recent_candle_data数据合并
        symbol_candle_data = symbol_candle_data_append_recent_candle_data(symbol_candle_data, recent_candle_data,
                                                                          symbol_config, max_candle_num)

        # ==========计算每个币种的交易信号==========
        logger.debug(f"策略名称：{symbol_config[symbol]['strategy_name']}")
        symbol_signal = calculate_signal(symbol_info, symbol_config, symbol_candle_data)
        logger.info('\n产生信号时间:\n' + str(symbol_info[['当前价格', '持仓方向', '目标持仓', '信号时间']]))
        logger.info('\n本周期交易计划:' + str(symbol_signal))

        # ==========下单==========
        exchange.timeout = exchange_timeout  # 下单时需要增加timeout的时间，将timout恢复正常
        # 计算下单信息
        symbol_order_params = cal_all_order_info(symbol_signal, symbol_info, symbol_config, exchange, logger)
        if symbol_order_params:
            logger.critical('\n订单参数\n' + str(symbol_order_params))

            logger.debug('下单线程状态：')
            _ = True  # 检查所有算法线程状态，全部都停止运行才可以继续下一次的下单。否则基于同样的持仓，会导致重复交易了。
            for item in pool:
                status = item.is_alive()
                logger.debug(f'{item.ident}::{status}')
                if status:
                    _ = False
            # 开始批量下单
            if _:  # 已经有下单线程在运行的情况下，不要再次进行交易，否则会导致异常
                pool = place_binance_batch_order_twap(exchange, symbol_order_params, symbol_config, logger=logger)
            else:
                logger.error('已经到下一轮运行，算法单还未完成！请检查！')

        # 本次循环结束
        logger.info('\n' + '-' * 40 + '本次循环结束，%d秒后进入下一次循环' % long_sleep_time + '-' * 40 + '\n\n')
        time.sleep(long_sleep_time)


if __name__ == '__main__':
    # ==========配置运行相关参数==========
    # =k线周期
    time_interval = '5m'  # 目前支持5m，15m，30m，1h，2h等。得交易所支持的K线才行。最好不要低于5m

    # =交易所配置, 这里只是写个示例，免得不知道怎么写json配置文件
    BINANCE_CONFIG = {
        'apiKey': '',
        'secret': '',
        'timeout': exchange_timeout,
        'rateLimit': 10,
        'verbose': False,
        'hostname': 'fapi.binance.com',
        'enableRateLimit': False,
        'proxies': {'https': "http://127.0.0.1:7890", 'http': "http://127.0.0.1:7890"}
    }
    with open('../config/ETH_boll_config.json') as f:
        cfg = json.load(f)
        if cfg['test']:
            BINANCE_CONFIG = cfg['exchange_test']
            exchange = ccxt.binance(BINANCE_CONFIG)  # 交易所api
            exchange.set_sandbox_mode(True)
        else:
            BINANCE_CONFIG = cfg['exchange']
            exchange = ccxt.binance(BINANCE_CONFIG)  # 交易所api


    # ==========配置策略相关参数==========
    # =symbol_config，更新需要交易的合约、策略参数、下单量等配置信息。主键为u本位合约的symbol。比特币永续为BTCUSDT，交割为BTCUSDT_210625
    symbol_config = {
        'ETHUSDT': {'leverage': 1,
                    'strategy_name': 'real_signal_random',
                    'para': [660, 1],
                    'position': 0.1,
                    },
    }
    symbol_config = cfg['stg']
    logger = init_logger(cfg['log'])
    while True:
        try:
            logger.critical('启动成功，进入循环！')
            main(exchange, cfg, symbol_config, logger)
        except Exception as e:
            logger.error('系统出错，10s之后重新运行，出错原因：' + traceback.format_exc())
            time.sleep(long_sleep_time)

